// Copyright (c) 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "net/tools/quic/quic_client_base.h"

#include "net/quic/crypto/quic_random.h"
#include "net/quic/quic_server_id.h"

namespace net {

QuicClientBase::QuicClientBase(const QuicServerId& server_id,
    const QuicVersionVector& supported_versions,
    const QuicConfig& config,
    QuicConnectionHelperInterface* helper,
    QuicAlarmFactory* alarm_factory,
    ProofVerifier* proof_verifier)
    : server_id_(server_id)
    , config_(config)
    , crypto_config_(proof_verifier)
    , helper_(helper)
    , alarm_factory_(alarm_factory)
    , supported_versions_(supported_versions)
    , initial_max_packet_length_(0)
    , num_stateless_rejects_received_(0)
    , num_sent_client_hellos_(0)
    , connection_error_(QUIC_NO_ERROR)
    , connected_or_attempting_connect_(false)
{
}

QuicClientBase::~QuicClientBase() { }

bool QuicClientBase::Initialize()
{
    num_sent_client_hellos_ = 0;
    num_stateless_rejects_received_ = 0;
    connection_error_ = QUIC_NO_ERROR;
    connected_or_attempting_connect_ = false;
    return true;
}

ProofVerifier* QuicClientBase::proof_verifier() const
{
    return crypto_config_.proof_verifier();
}

QuicClientSession* QuicClientBase::CreateQuicClientSession(
    QuicConnection* connection)
{
    session_.reset(new QuicClientSession(config_, connection, server_id_,
        &crypto_config_, &push_promise_index_));
    if (initial_max_packet_length_ != 0) {
        session()->connection()->SetMaxPacketLength(initial_max_packet_length_);
    }
    return session_.get();
}

bool QuicClientBase::EncryptionBeingEstablished()
{
    return !session_->IsEncryptionEstablished() && session_->connection()->connected();
}

QuicSpdyClientStream* QuicClientBase::CreateReliableClientStream()
{
    if (!connected()) {
        return nullptr;
    }

    return session_->CreateOutgoingDynamicStream(kDefaultPriority);
}

void QuicClientBase::WaitForStreamToClose(QuicStreamId id)
{
    DCHECK(connected());

    while (connected() && !session_->IsClosedStream(id)) {
        WaitForEvents();
    }
}

void QuicClientBase::WaitForCryptoHandshakeConfirmed()
{
    DCHECK(connected());

    while (connected() && !session_->IsCryptoHandshakeConfirmed()) {
        WaitForEvents();
    }
}

bool QuicClientBase::connected() const
{
    return session_.get() && session_->connection() && session_->connection()->connected();
}

bool QuicClientBase::goaway_received() const
{
    return session_ != nullptr && session_->goaway_received();
}

int QuicClientBase::GetNumSentClientHellos()
{
    // If we are not actively attempting to connect, the session object
    // corresponds to the previous connection and should not be used.
    const int current_session_hellos = !connected_or_attempting_connect_
        ? 0
        : session_->GetNumSentClientHellos();
    return num_sent_client_hellos_ + current_session_hellos;
}

void QuicClientBase::UpdateStats()
{
    num_sent_client_hellos_ += session()->GetNumSentClientHellos();
    if (session()->error() == QUIC_CRYPTO_HANDSHAKE_STATELESS_REJECT) {
        ++num_stateless_rejects_received_;
    }
}

int QuicClientBase::GetNumReceivedServerConfigUpdates()
{
    // If we are not actively attempting to connect, the session object
    // corresponds to the previous connection and should not be used.
    // We do not need to take stateless rejects into account, since we
    // don't expect any scup messages to be sent during a
    // statelessly-rejected connection.
    return !connected_or_attempting_connect_
        ? 0
        : session_->GetNumReceivedServerConfigUpdates();
}

QuicErrorCode QuicClientBase::connection_error() const
{
    // Return the high-level error if there was one.  Otherwise, return the
    // connection error from the last session.
    if (connection_error_ != QUIC_NO_ERROR) {
        return connection_error_;
    }
    if (session_.get() == nullptr) {
        return QUIC_NO_ERROR;
    }
    return session_->error();
}

QuicConnectionId QuicClientBase::GetNextConnectionId()
{
    QuicConnectionId server_designated_id = GetNextServerDesignatedConnectionId();
    return server_designated_id ? server_designated_id
                                : GenerateNewConnectionId();
}

QuicConnectionId QuicClientBase::GetNextServerDesignatedConnectionId()
{
    QuicCryptoClientConfig::CachedState* cached = crypto_config_.LookupOrCreate(server_id_);
    // If the cached state indicates that we should use a server-designated
    // connection ID, then return that connection ID.
    CHECK(cached != nullptr) << "QuicClientCryptoConfig::LookupOrCreate returned "
                             << "unexpected nullptr.";
    return cached->has_server_designated_connection_id()
        ? cached->GetNextServerDesignatedConnectionId()
        : 0;
}

QuicConnectionId QuicClientBase::GenerateNewConnectionId()
{
    return QuicRandom::GetInstance()->RandUint64();
}

} // namespace net
